// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/test/gtest_xml_util.h"

#include <stdint.h>

#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/strings/stringprintf.h"
#include "base/test/gtest_util.h"
#include "base/test/launcher/test_launcher.h"
#include "third_party/libxml/chromium/libxml_utils.h"

namespace base {

namespace {

    // This is used for the xml parser to report errors. This assumes the context
    // is a pointer to a std::string where the error message should be appended.
    static void XmlErrorFunc(void* context, const char* message, ...)
    {
        va_list args;
        va_start(args, message);
        std::string* error = static_cast<std::string*>(context);
        StringAppendV(error, message, args);
        va_end(args);
    }

} // namespace

bool ProcessGTestOutput(const base::FilePath& output_file,
    std::vector<TestResult>* results,
    bool* crashed)
{
    DCHECK(results);

    std::string xml_contents;
    if (!ReadFileToString(output_file, &xml_contents))
        return false;

    // Silence XML errors - otherwise they go to stderr.
    std::string xml_errors;
    ScopedXmlErrorFunc error_func(&xml_errors, &XmlErrorFunc);

    XmlReader xml_reader;
    if (!xml_reader.Load(xml_contents))
        return false;

    enum {
        STATE_INIT,
        STATE_TESTSUITE,
        STATE_TESTCASE,
        STATE_FAILURE,
        STATE_END,
    } state
        = STATE_INIT;

    while (xml_reader.Read()) {
        xml_reader.SkipToElement();
        std::string node_name(xml_reader.NodeName());

        switch (state) {
        case STATE_INIT:
            if (node_name == "testsuites" && !xml_reader.IsClosingElement())
                state = STATE_TESTSUITE;
            else
                return false;
            break;
        case STATE_TESTSUITE:
            if (node_name == "testsuites" && xml_reader.IsClosingElement())
                state = STATE_END;
            else if (node_name == "testsuite" && !xml_reader.IsClosingElement())
                state = STATE_TESTCASE;
            else
                return false;
            break;
        case STATE_TESTCASE:
            if (node_name == "testsuite" && xml_reader.IsClosingElement()) {
                state = STATE_TESTSUITE;
            } else if (node_name == "x-teststart" && !xml_reader.IsClosingElement()) {
                // This is our custom extension that helps recognize which test was
                // running when the test binary crashed.
                TestResult result;

                std::string test_case_name;
                if (!xml_reader.NodeAttribute("classname", &test_case_name))
                    return false;
                std::string test_name;
                if (!xml_reader.NodeAttribute("name", &test_name))
                    return false;
                result.full_name = FormatFullTestName(test_case_name, test_name);

                result.elapsed_time = TimeDelta();

                // Assume the test crashed - we can correct that later.
                result.status = TestResult::TEST_CRASH;

                results->push_back(result);
            } else if (node_name == "testcase" && !xml_reader.IsClosingElement()) {
                std::string test_status;
                if (!xml_reader.NodeAttribute("status", &test_status))
                    return false;

                if (test_status != "run" && test_status != "notrun")
                    return false;
                if (test_status != "run")
                    break;

                TestResult result;

                std::string test_case_name;
                if (!xml_reader.NodeAttribute("classname", &test_case_name))
                    return false;
                std::string test_name;
                if (!xml_reader.NodeAttribute("name", &test_name))
                    return false;
                result.full_name = test_case_name + "." + test_name;

                std::string test_time_str;
                if (!xml_reader.NodeAttribute("time", &test_time_str))
                    return false;
                result.elapsed_time = TimeDelta::FromMicroseconds(
                    static_cast<int64_t>(strtod(test_time_str.c_str(), NULL) * Time::kMicrosecondsPerSecond));

                result.status = TestResult::TEST_SUCCESS;

                if (!results->empty() && results->at(results->size() - 1).full_name == result.full_name && results->at(results->size() - 1).status == TestResult::TEST_CRASH) {
                    // Erase the fail-safe "crashed" result - now we know the test did
                    // not crash.
                    results->pop_back();
                }

                results->push_back(result);
            } else if (node_name == "failure" && !xml_reader.IsClosingElement()) {
                std::string failure_message;
                if (!xml_reader.NodeAttribute("message", &failure_message))
                    return false;

                DCHECK(!results->empty());
                results->at(results->size() - 1).status = TestResult::TEST_FAILURE;

                state = STATE_FAILURE;
            } else if (node_name == "testcase" && xml_reader.IsClosingElement()) {
                // Deliberately empty.
            } else {
                return false;
            }
            break;
        case STATE_FAILURE:
            if (node_name == "failure" && xml_reader.IsClosingElement())
                state = STATE_TESTCASE;
            else
                return false;
            break;
        case STATE_END:
            // If we are here and there are still XML elements, the file has wrong
            // format.
            return false;
        }
    }

    *crashed = (state != STATE_END);
    return true;
}

} // namespace base
